/**
 *  \file parameter.c
 *
 *  \brief
 *
 *  \author   <@linde-mh.de>
 *
 *
 */

/*===============================================[ private includes  ]================================================*/
#define PARAMETER_LINKAGE

#include "parameter.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include "../secret/secret.h"
#include "../md5/md5.h"
#include "../ini/ini.h"
#include <stdlib.h>

/*===============================================[ private defines   ]================================================*/

/*===============================================[ private datatypes ]================================================*/

/*===============================================[ private variables ]================================================*/
static U16 card_count;
static U16 master_card_count;
static BO project_ini_valid = FALSE;

#ifdef _WIN32
static const int PATHSEP = (int) '\\';
#else
static const int PATHSEP = (int)'/';
#endif

/*===============================================[ public variables  ]================================================*/
struct parameter parameter;

/*===============================================[ private functions ]================================================*/
void parameter_set_default_values(struct parameter *parameter);
static RC md5_file_digest(CH *filename, U32 digest[MD5_NO_OF_HASH_PARTS]);
static BO check_file_signature(CH *fh_any, CH *fh_md5);
static int ini_handler(void* user, const char* section, const char* name, const char* value);
static U8 hex_to_int(CH value);
//static RC calculate_md5_file_digest(CH *filename, U32 restore[MD5_NO_OF_HASH_PARTS], U32 digest[MD5_NO_OF_HASH_PARTS]);
static void read_hashkey(void* dest, void* src);
/*====================================================================================================================*/

/**
 *  Initialization.
 *
 *  Initialize the parameter module.
 *
 *  \return RC_SUCCESS if successful.
 */
RC parameter_initialize(U32 argc, CH *argv[]) {
   CH md5_filename[FILENAME_MAX];
   CH ini_filename[FILENAME_MAX];
   CH master_ini_filename[FILENAME_MAX];
   CH master_md5_filename[FILENAME_MAX];
   U16 len;

   parameter_set_default_values(&parameter);

   if (argc != 2) {
      printf("Wrong number of arguments.\n");
      return RC_ERROR;
   }

   if (!GetModuleFileName(NULL, master_ini_filename, FILENAME_MAX)) {
      return RC_ERROR;
   }

   if (!GetModuleFileName(NULL, master_md5_filename, FILENAME_MAX)) {
      return RC_ERROR;
   }

   strcpy(md5_filename, argv[1]);
   len = strlen(md5_filename);
   md5_filename[len - 3] = 'm';
   md5_filename[len - 2] = 'd';
   md5_filename[len - 1] = '5';

   strcpy(ini_filename, argv[1]);
   len = strlen(ini_filename);
   ini_filename[len - 3] = 'i';
   ini_filename[len - 2] = 'n';
   ini_filename[len - 1] = 'i';

   len = strlen(master_ini_filename) - strlen("fulgurator.exe");
   master_ini_filename[len] = 0;
   strcat(master_ini_filename, "fulgurator.ini");

   len = strlen(master_md5_filename) - strlen("fulgurator.exe");
   master_md5_filename[len] = 0;
   strcat(master_md5_filename, "fulgurator.md5");

   card_count = 0;

   printf("Reading .ini-files\n");

   /* try to parse master-ini */
   if (ini_parse((char*) master_ini_filename, ini_handler, &parameter) == 0) {
      printf("\tMaster-ini successfully parsed.\n");

      /* check if master-ini is valid */
      if (check_file_signature(master_ini_filename, master_md5_filename)) {
         printf("\tChecksum of master-ini is valid.\n");
      } else {
         /* error message will be printed by 'check_file_signature' */
         printf("\tContinuing without master-ini.\n");
         parameter_set_default_values(&parameter);
         card_count = 0;
      }
   } else {
      printf("\tError: Master-ini could not be parsed.\n");
      printf("\tContinuing without master-ini.\n");
      parameter_set_default_values(&parameter);
      card_count = 0;
   }

   /* store how many CAN-card numbers have been found in the master-ini */
   master_card_count = card_count;

   /* try to parse project-ini */
   if (ini_parse((char*) ini_filename, ini_handler, &parameter) == 0) {
      printf("\tProject-ini successfully parsed.\n");

      /* check if project-ini is valid */
      if (check_file_signature(ini_filename, md5_filename)) {
         printf("\tChecksum of project-ini is valid.\n");
         project_ini_valid = TRUE;
      } else {
         /* error message will be printed by 'check_file_signature' */
         /* Nothing to do in this case. We will find out later if the checksum is actually important or not. */
      }
   } else {
      printf("\tError: Project-ini could not be parsed.\n");
      return RC_ERROR;
   }

   parameter.hex_file_name = argv[1];

   /* try to locate segmentation file */
   FILE* fs;
   if (parameter.segfile_valid && parameter.segfile[0] != '\0') {

      /* try to open the segmentation file from the current directory */
      if (NULL == (fs = fopen(parameter.segfile, "r"))) {

         /* if that didn't work, try to open the segmentation file from the directory where the .hex-file is located */
         char* lastSlash = strrchr(parameter.hex_file_name, PATHSEP);

         if (NULL != lastSlash && *(++lastSlash) != '\0') {
            ptrdiff_t pathLength = lastSlash - parameter.hex_file_name;
            char* newSegFile = calloc(pathLength + strlen(parameter.segfile) + 1, 1);

            if (NULL != newSegFile) {
               strncpy(newSegFile, parameter.hex_file_name, pathLength);
               strncpy(&newSegFile[pathLength], parameter.segfile, strlen(parameter.segfile) + 1);
               parameter.segfile = newSegFile;
            }
         }
      }
   }

   return RC_SUCCESS;
}

void * parameter_test_serial_number(U32 serial) {
   U16 count = 0;
   U32 digest[MD5_NO_OF_HASH_PARTS];

   /* check if serial number of can-card is in master-ini */
   while (parameter.cancard_serial_numbers_valid[count] && count < master_card_count) {
      if (serial == parameter.cancard_serial_numbers[count]) {
         printf("\nYour CAN-interface was found in the master-ini.\n\tChecksum tests in project-ini are disabled.\n");
         return parameter.cancard_name[count];
      }
      count++;
   }

   void* returnValue = NULL;

   /* check if serial number of can-card is in project-ini */
   count = master_card_count;
   while (parameter.cancard_serial_numbers_valid[count] && count < card_count) {
      if (serial == parameter.cancard_serial_numbers[count]) {
         printf("\nYour CAN-interface was found in the project-ini.\n\tChecksum tests in project-ini are enabled.\n");
         returnValue = parameter.cancard_name[count];
         break;
      }
      count++;
   }

   if (NULL == returnValue) {
      printf("\nError: Your CAN-interface (serial number %u) was not found in any valid .ini-file.\n", (unsigned int) serial);
      return NULL;
   }

   printf("\nVerifying file checksums\n");

   if (FALSE == project_ini_valid) {
      printf("\tError: Checksum of project-ini is invalid.\n");
      return NULL;
   }

   U8 digest_from_ini[16];

   /* check if .hex-file md5-checksum is valid */
   if (parameter.hex_checksum && *(parameter.hex_checksum) != '\0') {
      md5_file_digest(parameter.hex_file_name, digest);

      /* convert 32-character ASCII hex representation of checksum from .ini-file to 16 byte number */
      read_hashkey( (void*) &digest_from_ini, (void*) parameter.hex_checksum);

      /* compare checksums */
      if (0 != memcmp(digest, digest_from_ini, 16)) {
         printf("\tError: Checksum of .hex-file is invalid.\n");
         return NULL;
      }
   }

   /* check if .seg-file md5-checksum is valid */
   if (parameter.seg_checksum && *(parameter.seg_checksum) != '\0') {
      md5_file_digest(parameter.segfile, digest);

      /* convert 32-character ASCII hex representation of checksum from .ini-file to 16 byte number */

      read_hashkey( (void*) &digest_from_ini, (void*) parameter.hex_checksum);

      /* compare checksums */
      if (0 != memcmp(digest, digest_from_ini, 16)) {
         printf("\tError: Checksum of segmentation file is invalid.\n");
         return NULL;
      }
   }

   return returnValue;
}

static RC md5_file_digest(CH *filename, U32 digest[MD5_NO_OF_HASH_PARTS]) {

   FILE *fh;
   sMD5_state_t c;
   S16 data;

   fh = fopen(filename, "rb");

   md5_initialize(&c);
   md5_update(&c, (const U8*)SECRET, SECRET_LENGHT );

   if (NULL == fh)
   {
      return RC_ERROR;
   }

   while ((data = fgetc(fh)) != EOF)
   {
      U8 byte = data;
      md5_update(&c, &byte, 1);
   }
   md5_finalize(&c,0U);

   U8 i = 0U;
   for ( i = 0U; i < MD5_NO_OF_HASH_PARTS; i++ )
   {
      digest[i] = c.bHash[i];
   }

   fclose(fh);
   return RC_SUCCESS;
}

//static RC calculate_md5_file_digest(CH *filename, U32 restore[MD5_NO_OF_HASH_PARTS], U32 digest[MD5_NO_OF_HASH_PARTS]) {
//   FILE *fh;
//   sMD5_state_t c;
//   S16 data;
//
//   fh = fopen(filename, "rb");
//
//   if (NULL == fh)
//   {
//      printf("\tError:\"%s\" has not been found.\n", filename);
//      return RC_ERROR;
//   }
//   md5_initialize(&c);
//
//   if (NULL != restore)
//   {
//	  md5_set_start_hash(&c, restore[0U], restore[1U], restore[2U], restore[3U] );
//   }
//   while ((data = fgetc(fh)) != EOF)
//   {
//      U8 byte = data;
//      md5_update(&c, &byte, 1);
//   }
//   md5_finalize(&c,0U);
//
//   U8 i = 0U;
//   for ( i = 0U; i < MD5_NO_OF_HASH_PARTS; i++ )
//   {
//      digest[i] = c.bHash[i];
//   }
//
//   fclose(fh);
//   return RC_SUCCESS;
//}

static BO check_file_signature(CH *any_filename, CH *md5_filename) {
   FILE *fh;
   U16 count;
   BO rv = TRUE;
   U32 digest[MD5_NO_OF_HASH_PARTS];
   U8 * hash = (U8 *)digest;

   md5_file_digest(any_filename, digest);

   fh = fopen(md5_filename, "rb");

   if (NULL == fh) {
      printf("\tError: \"%s\" has not been found.\n", md5_filename);
      return FALSE;
   }

   for (count = 0; count < (MD5_NO_OF_HASH_PARTS*4U); count++)
   {
      if ( fgetc(fh) != hash[count] )
      {
         printf("\tError: MD5-checksum of \"%s\" is invalid.\n", any_filename);
         rv = FALSE;
         break;
      }
   }
   fclose(fh);
   return rv;
}

/* Convert a 32-character string (src), which is an ASCII-representation of an 16-byte number (2 ASCII-characters per
 * byte), into that 16-byte number (dest).
 *
 * \param   dest  Pointer to a 16-byte array, which will store the output number.
 * \param   src   Pointer to a 32-byte char array, which contains the ASCII-representation of the 16-byte number.
 */
static void read_hashkey(void* dest, void* src) {
   CH *d = dest;
   CH *s = src;
   UX8 count;

   for (count = 0; count < 16; count++) {
      *d = (hex_to_int(*s) << 4) + hex_to_int(*(s + 1));
      s += 2;
      d++;
   }
}

static int ini_handler(void* user, const char* section, const char* name, const char* value) {
   struct parameter *p = user;

#define MATCH(s, n) stricmp(section, s) == 0 && stricmp(name, n) == 0

   if (MATCH("can", "baud")) {
      p->can_baud = atol(value);
      p->can_baud_valid = TRUE;
   } else if (MATCH("kwp", "tx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->kwp_tx_id));
      p->kwp_tx_id_valid = TRUE;
   } else if (MATCH("kwp", "rx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->kwp_rx_id));
      p->kwp_rx_id_valid = TRUE;
   } else if (MATCH("kwp", "common_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->kwp_common_id));
      p->kwp_common_id_valid = TRUE;
   } else if (MATCH("kwp", "init_01_tx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->init_01_tx_id));
      p->init_01_tx_id_valid = TRUE;
   } else if (MATCH("kwp", "init_01_rx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->init_01_rx_id));
      p->init_01_rx_id_valid = TRUE;
   } else if (MATCH("kwp", "hwid_tx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->hwid_tx_id));
      p->hwid_tx_id_valid = TRUE;
   } else if (MATCH("kwp", "hwid_rx_id")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->hwid_rx_id));
      p->hwid_rx_id_valid = TRUE;
   } else if (MATCH("options", "emergency_start")) {
      p->emergency_start = atol(value);
      p->emergency_start_valid = TRUE;
   } else if (MATCH("options", "automatic_detection")) {
      p->automatic_detection = atol(value);
      p->automatic_detection_valid = TRUE;
   } else if (MATCH("options", "emergency_duration")) {
      p->emergency_duration = atol(value);
      p->emergency_duration_valid = TRUE;
   } else if (MATCH("options", "address_size")) {
      p->address_size = atol(value);
      p->address_size_valid = TRUE;
   } else if (MATCH("options", "use_manual_separation_time")) {
      p->use_manual_separation_time = atol(value);
      p->use_manual_separation_time_valid = TRUE;
   } else if (MATCH("options", "manual_separation_time")) {
      p->manual_separation_time = atol(value);
      p->manual_separation_time_valid = TRUE;
   } else if (MATCH("options", "reset_after_programming")) {
      p->reset_after_programming = atol(value);
      p->reset_after_programming_valid = TRUE;
   } else if (MATCH("options", "wait_before_exit")) {
      p->wait_before_exit = atol(value);
      p->wait_before_exit_valid = TRUE;
   } else if (MATCH("filling", "fillbyte")) {
      sscanf(value, "0x%X", (unsigned int*) (&p->fillbyte));
      p->fillbyte_valid = TRUE;
   } else if (MATCH("filling", "segfile")) {
      p->segfile = strdup(value);
      p->segfile_valid = TRUE;
   } else if (MATCH("filling", "fill_only_hexfile")) {
      p->fill_only_hexfile = atol(value);
      p->fill_only_hexfile_valid = TRUE;
   }  else if (MATCH("security", "hex_checksum")) {
      memcpy(p->hex_checksum, value, 32);
      p->hex_checksum_valid = TRUE;
   } else if (MATCH("security", "seg_checksum")) {
      memcpy(p->seg_checksum, value, 32);
      p->seg_checksum_valid = TRUE;
   } else if (MATCH("security", "check_hwid")) {
      p->check_hwid = strdup(value);
      p->check_hwid_valid = TRUE;
   } else if (0 == stricmp("can_cards", section)) {
      p->cancard_name[card_count] = strdup(name);
      p->cancard_serial_numbers[card_count] = atol(value);
      p->cancard_serial_numbers_valid[card_count] = TRUE;
      card_count++;
   }
   return 1;            /* ini parser module expects a non-zero return value for success */
}

void parameter_set_default_values(struct parameter *parameter) {
   free(parameter->segfile);
   free(parameter->check_hwid);

   memset(parameter, 0x00, sizeof(struct parameter));

   parameter->hex_file_name = NULL;

   /* [can] */
   parameter->can_baud = 0;
   parameter->can_baud_valid = FALSE;

   /* [kwp] */
   parameter->kwp_tx_id = 0;
   parameter->kwp_tx_id_valid = FALSE;

   parameter->kwp_rx_id = 0;
   parameter->kwp_rx_id_valid = FALSE;

   parameter->kwp_common_id = 780;
   parameter->kwp_common_id_valid = FALSE;

   parameter->init_01_tx_id = 0;
   parameter->init_01_tx_id_valid = FALSE;

   parameter->init_01_rx_id = 0;
   parameter->init_01_rx_id_valid = FALSE;

   parameter->hwid_tx_id = 0;
   parameter->hwid_tx_id_valid = FALSE;

   parameter->hwid_rx_id = 0;
   parameter->hwid_rx_id_valid = FALSE;

   /* [options] */
   parameter->emergency_start = 0;
   parameter->emergency_start_valid = FALSE;

   parameter->automatic_detection = FALSE;
   parameter->automatic_detection_valid = FALSE;

   parameter->emergency_duration = 5000;
   parameter->emergency_duration_valid = FALSE;

   parameter->address_size = 32;
   parameter->address_size_valid = FALSE;

   parameter->use_manual_separation_time = FALSE;
   parameter->use_manual_separation_time_valid = FALSE;

   parameter->manual_separation_time = 0;
   parameter->manual_separation_time_valid = FALSE;

   parameter->reset_after_programming = FALSE;
   parameter->reset_after_programming_valid = FALSE;

   parameter->wait_before_exit = FALSE;
   parameter->wait_before_exit_valid = FALSE;

   /* [filling] */
   parameter->fillbyte = 0;
   parameter->fillbyte_valid = FALSE;

   parameter->segfile = NULL;
   parameter->segfile_valid = FALSE;

   parameter->fill_only_hexfile = FALSE;
   parameter->fill_only_hexfile_valid = FALSE;

   /* [security] */
   parameter->hex_checksum[0] = '\0';
   parameter->hex_checksum_valid = FALSE;

   parameter->seg_checksum[0] = '\0';
   parameter->seg_checksum_valid = FALSE;

   parameter->check_hwid = NULL  ;
   parameter->check_hwid_valid = FALSE;

   /* [can-cards] */
   //parameter->cancard_serial_numbers[0x100];
   //parameter->cancard_serial_bumbers_valid[0x100];
}

static U8 hex_to_int(CH value) {
   switch (value) {
      case '0':
         return 0;
      case '1':
         return 1;
      case '2':
         return 2;
      case '3':
         return 3;
      case '4':
         return 4;
      case '5':
         return 5;
      case '6':
         return 6;
      case '7':
         return 7;
      case '8':
         return 8;
      case '9':
         return 9;
      case 'a':
      case 'A':
         return 10;
      case 'b':
      case 'B':
         return 11;
      case 'c':
      case 'C':
         return 12;
      case 'd':
      case 'D':
         return 13;
      case 'e':
      case 'E':
         return 14;
      case 'f':
      case 'F':
         return 15;
   }
   return 0xFF;
}

/***********************************************************************************************************************
 *                             Copyright 2010 Linde Material Handling. All rights reserved.                             *
 ***********************************************************************************************************************/
